#!/bin/bash

# Docker容器完整导出脚本
# 功能：导出所有容器、镜像、卷和网络配置
# 版本: 1.0
# 使用方法: ./docker_export.sh [导出目录]

set -e

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

# 日志函数
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }

# 显示帮助信息
show_help() {
    cat << EOF
Docker容器完整导出脚本

用法: $0 [选项] [导出目录]

功能:
  - 导出指定或所有运行中的容器配置
  - 导出容器使用的镜像
  - 导出所有卷数据
  - 导出网络配置
  - 生成容器映射文件

参数:
  导出目录    可选，默认为 docker_export_YYYYMMDD_HHMMSS

选项:
  -h, --help              显示此帮助信息
  -c, --containers LIST   指定要导出的容器（用逗号分隔）
  -a, --all              导出所有容器（默认）
  --skip-networks        跳过网络配置导出（推荐用于跨服务器迁移）

示例:
  $0                                    # 导出所有容器到默认目录
  $0 /path/to/backup                    # 导出所有容器到指定目录
  $0 -c "app1,app2" /path/to/backup      # 导出指定容器
  $0 --containers "web,db"              # 导出指定容器到默认目录
EOF
}

# 解析命令行参数
SPECIFIC_CONTAINERS=""
EXPORT_ALL=true
EXPORT_DIR=""
SKIP_NETWORKS=false

while [[ $# -gt 0 ]]; do
    case $1 in
        -h|--help)
            show_help
            exit 0
            ;;
        -c|--containers)
            SPECIFIC_CONTAINERS="$2"
            EXPORT_ALL=false
            shift 2
            ;;
        -a|--all)
            EXPORT_ALL=true
            shift
            ;;
        --skip-networks)
            SKIP_NETWORKS=true
            shift
            ;;
        -*)
            log_error "未知选项: $1"
            show_help
            exit 1
            ;;
        *)
            # 这是导出目录参数
            if [ -z "$EXPORT_DIR" ]; then
                EXPORT_DIR="$1"
            else
                log_error "多余的参数: $1"
                show_help
                exit 1
            fi
            shift
            ;;
    esac
done

# 设置默认导出目录
if [ -z "$EXPORT_DIR" ]; then
    EXPORT_DIR="docker_export_$(date +%Y%m%d_%H%M%S)"
fi

# 检查Docker状态
check_docker() {
    if ! command -v docker >/dev/null 2>&1; then
        log_error "Docker未安装"
        exit 1
    fi
    
    if ! docker info >/dev/null 2>&1; then
        log_error "Docker未运行或权限不足"
        exit 1
    fi
    
    log_success "Docker状态正常"
}

# 检查依赖工具
check_dependencies() {
    local missing_tools=()
    
    for tool in jq tar gzip; do
        if ! command -v "$tool" >/dev/null 2>&1; then
            missing_tools+=("$tool")
        fi
    done
    
    if [ ${#missing_tools[@]} -gt 0 ]; then
        log_error "缺少必要工具: ${missing_tools[*]}"
        log_info "请安装缺少的工具后重试"
        exit 1
    fi
    
    log_success "所有依赖工具已安装"
}

# 创建导出目录结构
create_export_structure() {
    log_info "创建导出目录: $EXPORT_DIR"
    
    if [ "$SKIP_NETWORKS" = true ]; then
        mkdir -p "$EXPORT_DIR"/{images,volumes,configs,bind_mounts}
        log_info "跳过网络目录创建（--skip-networks选项已启用）"
    else
        mkdir -p "$EXPORT_DIR"/{images,volumes,networks,configs,bind_mounts}
    fi
    
    if [ $? -eq 0 ]; then
        log_success "导出目录结构创建完成"
    else
        log_error "创建导出目录失败"
        exit 1
    fi
}

# 导出容器镜像
export_images() {
    log_info "开始导出容器镜像..."
    
    local exported_images=()
    local containers_to_export
    
    if [ "$EXPORT_ALL" = true ]; then
        containers_to_export=$(docker ps -q)
    else
        # 使用指定的容器列表
        local container_ids=""
        IFS=',' read -ra CONTAINER_ARRAY <<< "$SPECIFIC_CONTAINERS"
        for container_name in "${CONTAINER_ARRAY[@]}"; do
            container_name=$(echo "$container_name" | xargs)
            container_id=$(docker ps -q --filter "name=^${container_name}$")
            if [ -n "$container_id" ]; then
                container_ids="$container_ids $container_id"
            fi
        done
        containers_to_export="$container_ids"
    fi
    
    for container_id in $containers_to_export; do
        if [ "$container_id" != "" ]; then
            container_name=$(docker inspect "$container_id" --format '{{.Name}}' | sed 's/^\///')
            image_name=$(docker inspect "$container_id" --format '{{.Config.Image}}')
            
            # 创建安全的文件名
            safe_image_name=$(echo "$image_name" | sed 's/[^a-zA-Z0-9._-]/_/g')
            
            log_info "导出容器 $container_name 的镜像: $image_name"
            
            if [[ " ${exported_images[*]} " =~ " ${image_name} " ]]; then
                log_info "镜像 $image_name 已导出，跳过"
            else
                docker save "$image_name" > "$EXPORT_DIR/images/${safe_image_name}.tar"
                if [ $? -eq 0 ]; then
                    log_success "镜像 $image_name 导出完成"
                    exported_images+=("$image_name")
                else
                    log_error "镜像 $image_name 导出失败"
                fi
            fi
        fi
    done
    
    log_success "镜像导出完成"
}

# 导出卷数据
export_volumes() {
    log_info "开始导出卷数据..."
    
    local volumes_to_export=()
    local containers_to_export
    
    if [ "$EXPORT_ALL" = true ]; then
        containers_to_export=$(docker ps -q)
    else
        # 使用指定的容器列表
        local container_ids=""
        IFS=',' read -ra CONTAINER_ARRAY <<< "$SPECIFIC_CONTAINERS"
        for container_name in "${CONTAINER_ARRAY[@]}"; do
            container_name=$(echo "$container_name" | xargs)
            container_id=$(docker ps -q --filter "name=^${container_name}$")
            if [ -n "$container_id" ]; then
                container_ids="$container_ids $container_id"
            fi
        done
        containers_to_export="$container_ids"
    fi
    
    # 收集所有相关容器使用的卷
    for container_id in $containers_to_export; do
        if [ "$container_id" != "" ]; then
            container_volumes=$(docker inspect "$container_id" --format '{{range .Mounts}}{{if eq .Type "volume"}}{{.Name}} {{end}}{{end}}')
            for volume in $container_volumes; do
                if [[ ! " ${volumes_to_export[*]} " =~ " ${volume} " ]]; then
                    volumes_to_export+=("$volume")
                fi
            done
        fi
    done
    
    if [ ${#volumes_to_export[@]} -eq 0 ]; then
        log_info "没有找到需要导出的卷"
        return 0
    fi
    
    # 导出收集到的卷
    for volume_name in "${volumes_to_export[@]}"; do
        log_info "导出卷: $volume_name"
        
        # 使用临时容器导出卷数据
        docker run --rm -v "$volume_name:/volume_data" -v "$(pwd)/$EXPORT_DIR/volumes:/backup" alpine tar czf "/backup/${volume_name}.tar.gz" -C /volume_data .
        
        if [ $? -eq 0 ]; then
            log_success "卷 $volume_name 导出完成"
        else
            log_warning "卷 $volume_name 导出失败或为空"
        fi
    done
    
    log_success "卷数据导出完成"
}

# 导出绑定挂载文件
export_bind_mounts() {
    log_info "开始导出绑定挂载文件..."
    
    # 创建绑定挂载目录
    mkdir -p "$EXPORT_DIR/bind_mounts"
    
    local containers_to_export
    local bind_mounts_exported=()
    
    if [ "$EXPORT_ALL" = true ]; then
        containers_to_export=$(docker ps -q)
    else
        # 使用指定的容器列表
        local container_ids=""
        IFS=',' read -ra CONTAINER_ARRAY <<< "$SPECIFIC_CONTAINERS"
        for container_name in "${CONTAINER_ARRAY[@]}"; do
            container_name=$(echo "$container_name" | xargs)
            container_id=$(docker ps -q --filter "name=^${container_name}$")
            if [ -n "$container_id" ]; then
                container_ids="$container_ids $container_id"
            fi
        done
        containers_to_export="$container_ids"
    fi
    
    # 收集所有绑定挂载
    for container_id in $containers_to_export; do
        if [ "$container_id" != "" ]; then
            container_name=$(docker inspect "$container_id" --format '{{.Name}}' | sed 's/^\///')
            log_info "检查容器 $container_name 的绑定挂载..."
            
            # 获取绑定挂载信息
            while IFS= read -r mount_info; do
                if [ "$mount_info" != "" ]; then
                    mount_type=$(echo "$mount_info" | jq -r '.Type')
                    if [ "$mount_type" = "bind" ]; then
                        source_path=$(echo "$mount_info" | jq -r '.Source')
                        destination=$(echo "$mount_info" | jq -r '.Destination')
                        
                        # 检查源路径是否存在
                        if [ -e "$source_path" ]; then
                            # 避免重复导出相同的路径
                            if [[ ! " ${bind_mounts_exported[*]} " =~ " ${source_path} " ]]; then
                                log_info "导出绑定挂载: $source_path"
                                
                                # 提取容器服务名称（去掉项目前缀和实例后缀）
                                service_name=$(echo "$container_name" | sed 's/^[^-]*-//; s/-[0-9]*$//')
                                
                                # 基于容器内目标路径创建相对路径
                                relative_destination=$(echo "$destination" | sed 's|^/||')
                                
                                # 按照容器服务名称组织目录结构
                                target_path="$EXPORT_DIR/bind_mounts/$service_name/$relative_destination"
                                
                                # 创建目标目录
                                mkdir -p "$(dirname "$target_path")"
                                
                                # 复制文件或目录
                                if [ -d "$source_path" ]; then
                                    cp -r "$source_path"/* "$target_path/" 2>/dev/null || cp -r "$source_path" "$(dirname "$target_path")"
                                    if [ $? -eq 0 ]; then
                                        log_success "目录 $source_path 导出完成 -> $service_name/$relative_destination"
                                    else
                                        log_error "目录 $source_path 导出失败"
                                    fi
                                elif [ -f "$source_path" ]; then
                                    cp "$source_path" "$target_path"
                                    if [ $? -eq 0 ]; then
                                        log_success "文件 $source_path 导出完成 -> $service_name/$relative_destination"
                                    else
                                        log_error "文件 $source_path 导出失败"
                                    fi
                                fi
                                
                                bind_mounts_exported+=("$source_path")
                                
                                # 记录绑定挂载映射（使用服务名称和相对目标路径）
                                echo "$container_name,$service_name/$relative_destination,$destination" >> "$EXPORT_DIR/bind_mounts_mapping.csv"
                            else
                                log_info "绑定挂载 $source_path 已导出，跳过"
                            fi
                        else
                            log_warning "绑定挂载源路径不存在: $source_path"
                        fi
                    fi
                fi
            done < <(docker inspect "$container_id" --format '{{json .Mounts}}' | jq -c '.[]?' 2>/dev/null || echo "")
        fi
    done
    
    # 创建绑定挂载映射文件头
    if [ ! -f "$EXPORT_DIR/bind_mounts_mapping.csv" ]; then
        echo "container_name,source_path,destination_path" > "$EXPORT_DIR/bind_mounts_mapping.csv"
        log_info "没有找到需要导出的绑定挂载"
    else
        # 添加文件头
        sed -i '1i\container_name,source_path,destination_path' "$EXPORT_DIR/bind_mounts_mapping.csv"
        log_success "绑定挂载导出完成，共导出 ${#bind_mounts_exported[@]} 个路径"
    fi
}

# 导出网络配置
export_networks() {
    if [ "$SKIP_NETWORKS" = true ]; then
        log_info "跳过网络配置导出（--skip-networks选项已启用）"
        return 0
    fi
    
    log_info "开始导出网络配置..."
    
    local networks_to_export=()
    local containers_to_export
    
    if [ "$EXPORT_ALL" = true ]; then
        containers_to_export=$(docker ps -q)
    else
        # 使用指定的容器列表
        local container_ids=""
        IFS=',' read -ra CONTAINER_ARRAY <<< "$SPECIFIC_CONTAINERS"
        for container_name in "${CONTAINER_ARRAY[@]}"; do
            container_name=$(echo "$container_name" | xargs)
            container_id=$(docker ps -q --filter "name=^${container_name}$")
            if [ -n "$container_id" ]; then
                container_ids="$container_ids $container_id"
            fi
        done
        containers_to_export="$container_ids"
    fi
    
    # 收集所有相关容器使用的网络
    for container_id in $containers_to_export; do
        if [ "$container_id" != "" ]; then
            container_networks=$(docker inspect "$container_id" --format '{{range $net, $conf := .NetworkSettings.Networks}}{{$net}} {{end}}')
            for network in $container_networks; do
                if [ "$network" != "bridge" ] && [ "$network" != "host" ] && [ "$network" != "none" ] && [[ ! " ${networks_to_export[*]} " =~ " ${network} " ]]; then
                    networks_to_export+=("$network")
                fi
            done
        fi
    done
    
    if [ ${#networks_to_export[@]} -eq 0 ]; then
        log_info "没有找到需要导出的自定义网络"
        return 0
    fi
    
    # 导出收集到的网络
    for network_name in "${networks_to_export[@]}"; do
        log_info "导出网络: $network_name"
        
        docker network inspect "$network_name" > "$EXPORT_DIR/networks/${network_name}.json"
        
        if [ $? -eq 0 ]; then
            log_success "网络 $network_name 配置导出完成"
        else
            log_error "网络 $network_name 配置导出失败"
        fi
    done
    
    log_success "网络配置导出完成"
}

# 导出容器配置
export_container_configs() {
    log_info "开始导出容器配置..."
    
    # 创建容器映射文件
    echo "container_id,container_name,image_name,safe_image_name" > "$EXPORT_DIR/container_mapping.csv"
    
    local containers_to_export
    
    if [ "$EXPORT_ALL" = true ]; then
        # 获取所有运行中的容器ID
        containers_to_export=$(docker ps -q)
        log_info "导出所有运行中的容器配置"
    else
        # 使用指定的容器列表
        log_info "导出指定容器配置: $SPECIFIC_CONTAINERS"
        
        # 将逗号分隔的容器名转换为容器ID
        local container_ids=""
        IFS=',' read -ra CONTAINER_ARRAY <<< "$SPECIFIC_CONTAINERS"
        for container_name in "${CONTAINER_ARRAY[@]}"; do
            container_name=$(echo "$container_name" | xargs)  # 去除空格
            container_id=$(docker ps -q --filter "name=^${container_name}$")
            if [ -n "$container_id" ]; then
                container_ids="$container_ids $container_id"
            else
                log_warning "容器 '$container_name' 不存在或未运行，将跳过"
            fi
        done
        containers_to_export="$container_ids"
    fi
    
    if [ -z "$containers_to_export" ]; then
        log_warning "没有找到符合条件的运行中容器"
        return 0
    fi
    
    for container_id in $containers_to_export; do
        if [ "$container_id" != "" ]; then
            container_name=$(docker inspect "$container_id" --format '{{.Name}}' | sed 's/^\///')
            image_name=$(docker inspect "$container_id" --format '{{.Config.Image}}')
            safe_image_name=$(echo "$image_name" | sed 's/[^a-zA-Z0-9._-]/_/g')
            
            log_info "导出容器配置: $container_name"
            
            # 导出容器详细配置
            docker inspect "$container_id" > "$EXPORT_DIR/configs/${container_name}_inspect.json"
            
            # 记录到映射文件
            echo "$container_id,$container_name,$image_name,$safe_image_name" >> "$EXPORT_DIR/container_mapping.csv"
            
            if [ $? -eq 0 ]; then
                log_success "容器 $container_name 配置导出完成"
            else
                log_error "容器 $container_name 配置导出失败"
            fi
        fi
    done
    
    log_success "容器配置导出完成"
}

# 生成导出报告
generate_report() {
    log_info "生成导出报告..."
    
    cat > "$EXPORT_DIR/export_report.txt" << EOF
Docker容器导出报告
==================

导出时间: $(date)
导出目录: $EXPORT_DIR

导出统计:
- 容器数量: $(docker ps -q | wc -l)
- 镜像数量: $(ls "$EXPORT_DIR/images/"*.tar 2>/dev/null | wc -l)
- 卷数量: $(ls "$EXPORT_DIR/volumes/"*.tar.gz 2>/dev/null | wc -l)
- 网络数量: $(ls "$EXPORT_DIR/networks/"*.json 2>/dev/null | wc -l)

目录结构:
$(tree "$EXPORT_DIR" 2>/dev/null || find "$EXPORT_DIR" -type f | sort)

使用说明:
1. 将整个 $EXPORT_DIR 目录复制到目标服务器
2. 在目标服务器上运行 docker_import.sh 进行还原

注意事项:
- 请确保目标服务器已安装Docker
- 建议在还原前备份目标服务器的现有容器
- 如遇网络冲突，导入脚本会自动处理
EOF
    
    log_success "导出报告生成完成"
}

# 主函数
main() {
    echo "Docker容器完整导出脚本"
    echo "========================"
    echo
    
    # 检查环境
    check_docker
    check_dependencies
    echo
    
    # 检查是否有运行中的容器
    container_count=$(docker ps -q | wc -l)
    if [ "$container_count" -eq 0 ]; then
        log_warning "没有运行中的容器"
        read -p "是否继续导出镜像、卷和网络？(y/N): " -n 1 -r
        echo
        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
            log_info "导出已取消"
            exit 0
        fi
    else
        log_info "发现 $container_count 个运行中的容器"
    fi
    
    echo
    
    # 创建导出目录
    create_export_structure
    echo
    
    # 开始导出
    log_info "开始导出过程..."
    echo
    
    export_images
    echo
    
    export_volumes
    echo
    
    export_bind_mounts
    echo
    
    export_networks
    echo
    
    export_container_configs
    echo
    
    # 复制导入脚本
    log_info "复制导入脚本..."
    if [ -f "docker_import.sh" ]; then
        cp "docker_import.sh" "$EXPORT_DIR/"
        chmod +x "$EXPORT_DIR/docker_import.sh"
        log_success "导入脚本复制完成"
    else
        log_warning "未找到导入脚本 docker_import.sh"
    fi
    echo
    
    generate_report
    echo
    
    log_success "导出完成！"
    log_info "导出目录: $(realpath "$EXPORT_DIR")"
    log_info "请将整个目录复制到目标服务器，然后运行 docker_import.sh"
}

# 运行主函数
main "$@"